package lol.clann.minecraft.springboot.plugin.bukkit.config.jackson;

import com.google.auto.service.AutoService;
import lol.clann.minecraft.springboot.api.bukkit.constant.NBTTypeEnum;
import lol.clann.minecraft.springboot.api.bukkit.exception.NBTException;
import lol.clann.minecraft.springboot.api.bukkit.utils.NBTUtils;
import lol.clann.minecraft.springboot.api.context.SpringContext;
import lol.clann.minecraft.springboot.api.model.LazyField;
import lol.clann.minecraft.springboot.api.spi.jackson.JacksonDeserializer;
import lol.clann.minecraft.springboot.bukkit.utils.NMSClasses;
import lol.clann.minecraft.springboot.plugin.core.config.jackson.fill.BaseJsonDeserializer;
import relocate.com.fasterxml.jackson.core.JsonParser;
import relocate.com.fasterxml.jackson.core.JsonProcessingException;
import relocate.com.fasterxml.jackson.databind.DeserializationContext;
import relocate.com.fasterxml.jackson.databind.JsonNode;
import relocate.com.fasterxml.jackson.databind.ObjectMapper;
import relocate.com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

/**
 *
 * @author pyz
 * @date 2019/5/18 2:56 PM
 */
@AutoService(JacksonDeserializer.class)
public class NBTTagCompoundDeserializer extends BaseJsonDeserializer<Object> implements JacksonDeserializer {

    private LazyField<NBTUtils> nbtUtils = LazyField.of(() -> SpringContext.getBean(NBTUtils.class));
    private LazyField<ObjectMapper> objectMapper = LazyField.of(() -> SpringContext.getBean(ObjectMapper.class));

    @Override
    public Class getType() {
        return NMSClasses.getNBTTagCompound();
    }

    /**
     * 反序列化(创建)
     *
     * @param p
     * @param context
     * @return
     * @throws IOException
     * @throws JsonProcessingException
     */
    @Override
    protected Object create(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException {
        Object tag = nbtUtils.get().newNBTTagCompound();
        Map<String, Object> tagMap = (Map<String, Object>) nbtUtils.get().getValue(tag);
        ObjectNode tree = objectMapper.get().readTree(p);
        Iterator<Map.Entry<String, JsonNode>> it = tree.fields();
        while (it.hasNext()) {
            Map.Entry<String, JsonNode> next = it.next();
            if (next.getValue().isObject()) {
                tagMap.put(next.getKey(), objectMapper.get().readValue(next.getValue().traverse(), NMSClasses.getNBTTagCompound()));
            } else if (next.getValue().isArray()) {
                tagMap.put(next.getKey(), objectMapper.get().readValue(next.getValue().traverse(), NMSClasses.getNBTTagList()));
            } else {
                String content = next.getValue().asText();
                String identity = content.substring(0, 2);
                NBTTypeEnum type = NBTTypeEnum.getByIdentity(identity);
                if (type == null) {
                    throw new NBTException("can not resolve nbt value:" + content);
                }
                Object v;
                if (type.getField().getField().getType().isArray()) {
                    v = objectMapper.get().readValue(content.substring(2), type.getField().getField().getType());
                } else {
                    v = objectMapper.get().convertValue(content.substring(2), type.getField().getField().getType());
                }
                Object tagV = type.getConstructor().invoke(null);
                type.getField().set(tagV, v);
                tagMap.put(next.getKey(), tagV);
            }
        }
        return tag;
    }

    /**
     * 反序列化(填充)
     *
     * @param value
     * @param p
     * @param context
     * @return
     * @throws IOException
     * @throws JsonProcessingException
     */
    @Override
    protected Object fill(Object value, JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException {
        Object tag = create(p, context);
        Map<String, Object> oldTagMap = (Map<String, Object>) nbtUtils.get().getValue(value);
        Map<String, Object> tagMap = (Map<String, Object>) nbtUtils.get().getValue(tag);
        oldTagMap.clear();
        oldTagMap.putAll(tagMap);
        return value;
    }
}
